﻿/*
 * CMediaStorage.cpp
 *
 *  Created on: 2017年3月3日
 *      Author: chuanjiang.zh
 */

#include "CMediaStorage.h"
#include "Utility.h"
#include "CLog.h"
#include "Path.h"
#include "CDataStore.h"
#include "AppConfig.h"
#include "AppConst.h"
#include <errno.h>
#include "TSafeStr.h"
#include "MFormatUtil.h"
#include "TFileUtil.h"
#include <algorithm>

namespace av
{


struct IsDevice : public std::binary_function<MediaStorageChannel*, CMediaStorageChannelPtr, bool>
{
    IsDevice(const char* deviceID):
        m_deviceID(deviceID)
    {
    }

    bool operator () (MediaStorageChannel* key, CMediaStorageChannelPtr& chl) const
    {
        return m_deviceID == chl->getDevice();
    }

    std::string m_deviceID;
};

typedef std::function< void(MediaStorageChannel*, CMediaStorageChannelPtr&) >    MediaStorageChannelFunction;





CMediaStorage::CMediaStorage():
		m_channels(),
		m_fileFormat(kRawFile),
		m_fileDuration(DEFAULT_FILE_DURATION),
		m_dirPath(),
		m_sink(),
		m_dataStore(new CDataStore()),
		m_coverageRecord(DELETE_RECORD_COUNT),
		m_coverageEvents(),
		m_dbPath()
{
	m_dirPath = comn::Path::getWorkDir();

	m_diskMonitor.setListener(this);

}

CMediaStorage::~CMediaStorage()
{
	close();
}

void CMediaStorage::close()
{
    CLog::info("MediaStorage close.\n");

    m_diskMonitor.close();

    while (m_channels.size() > 0)
    {
        MediaStorageChannel* key = NULL;
        CMediaStorageChannelPtr chl;
        m_channels.pop_front(key, chl);

        if (chl)
        {
            chl->close();
        }
    }

    m_dataStore->close();

    if (m_clearThread)
    {
        m_clearThread->stop();
        m_clearThread.reset();
    }
}


bool CMediaStorage::open(const char* dbpath)
{
    CLog::info("MediaStorage open. %s\n", dbpath);

    if (dbpath != NULL)
    {
    	m_dbPath = dbpath;
    }

    if (m_dbPath.empty())
    {
        m_dbPath = AppConfig::instance().get("DB.Path", AppConst::DB_FILE);
    }

    if (!m_dataStore->open(m_dbPath))
    {
        CLog::error("can not open db. %s\n", m_dbPath.c_str());
        return false;
    }

    m_channels.clear();

    m_diskMonitor.open();

    return true;
}

bool CMediaStorage::isOpen()
{
    return m_dataStore->isOpen();
}


void CMediaStorage::setDbPath(const char* dbPath)
{
	if (dbPath && (strlen(dbPath) > 0))
	{
		m_dbPath = dbPath;
	}
}


MediaStorageChannel* CMediaStorage::openChannel(const char* device)
{
	CLog::info("CMediaStorage::openChannel %s\n", device);

    if (device == NULL || strlen(device) == 0)
    {
        return NULL;
    }

    if (!isOpen() && !open(""))
    {
        return NULL;
    }

    CMediaStorageChannel* channel = new CMediaStorageChannel();
	CMediaStorageChannelPtr chl(channel);

    channel->setDataStore(m_dataStore);
    channel->setFileDuration(m_fileDuration);
    channel->setRecordDir(m_dirPath);
    channel->setFileFormat(m_fileFormat);

	if (!channel->open(device))
	{
		return NULL;
	}

    m_channels.put(channel, chl);

	return channel;
}

void CMediaStorage::closeChannel(MediaStorageChannel* channel)
{
	if (!channel)
	{
		return;
	}

	CLog::info("CMediaStorage::closeChannel %s\n", channel->getDevice());

    CMediaStorageChannelPtr chl;
    m_channels.remove(channel, chl);
}

bool CMediaStorage::isChannelOpen(const char* device)
{
    IsDevice pred(device);
    MediaStorageChannel* key = NULL;
    CMediaStorageChannelPtr chl;
    return m_channels.findIf(pred, key, chl);
}

/**
 * 设置录像文件格式
 * 默认为TS文件
 * @param fileFormat
 */
void CMediaStorage::setFileFormat(RecordFileFormat fileFormat)
{
	CLog::info("MediaStorage setFileFormat. fmt:%d.\n", fileFormat);
	
	m_fileFormat = fileFormat;

	doSetFileFormat(m_fileFormat);
}

/**
 * 设置录像文件时长
 * 默认值为 StorageConst.DEFAULT_FILE_DURATION
 * 录像达到设定时长时, 切换文件
 * 切换文件发生在关键帧处, 因此,实际的录像文件时长有偏差, 偏差大小为关键帧间隔
 * @param seconds
 */
void CMediaStorage::setFileDuration(int seconds)
{
	CLog::info("MediaStorage setFileDuration. duration:%d.\n", seconds);

	if (seconds <= 0)
	{
		return;
	}

	m_fileDuration = seconds;

	doSetFileDuration(m_fileDuration);
}

int CMediaStorage::findRecord(const MRecordQuery& query, int offset, MRecord* records, int size)
{
	CLog::info("CMediaStorage::findRecord. m_dataStore: %p, offset: %d, max: %d", m_dataStore.get(), offset, size);

	return m_dataStore->searchRecord(query, offset, records, size);
}

int CMediaStorage::countRecord(const MRecordQuery& query)
{
    return m_dataStore->countRecord(query);
}

bool CMediaStorage::getRecord(int id, av::MRecord& record)
{
    return m_dataStore->getRecord(id, record);
}

bool CMediaStorage::writeEvent(const char* device, int event, const char* name, const char* memo)
{
	CLog::info("MediaStorage writeEvent. device:%s, event:%d, name:%s\n", device, event, name);

	av::AlarmEvent alarmEvent = av::AlarmEvent();
	comn::copyStr(alarmEvent.device, device);
	alarmEvent.type = event;
	alarmEvent.startTime = time(NULL);
	comn::copyStr(alarmEvent.name, name);
	comn::copyStr(alarmEvent.memo, memo);

	return writeEvent(device, alarmEvent);
}

int CMediaStorage::findEvent(const char* device, time_t fromTime, time_t toTime, int types, AlarmEvent* alarmEvents, int size)
{
	return m_dataStore->searchEvent(device, fromTime, toTime, types, alarmEvents, size);
}

/**
 * 设置事件回调
 * @param sink
 */
void CMediaStorage::setEventSink(MediaStorageSink* sink)
{
	m_sink = sink;
}

/**
 * 设置录像目录
 * @param path	目录
 * @param percentage	磁盘最大使用比例. 取值范围: (0.0, 1.0)
 * @return
 */
bool CMediaStorage::setRecordDir(const char* path, float percentage)
{
	CLog::info("MediaStorage setRecordDir. path:%s, percent:%f\n", path, percentage);

	if (!path || (strlen(path) == 0))
	{
		return false;
	}

	std::string dirpath(path);
	if (!comn::Path::exist(path))
	{
		if (!comn::Path::createDirectories(dirpath))
		{
			CLog::error("failed to create record dir. %s\n", dirpath.c_str());
		}
	}

	m_dirPath = dirpath;

	double freePercent = 1.0 - percentage;
	if (freePercent < 0)
	{
		freePercent = 0.1;
	}

	m_diskMonitor.clear();
	m_diskMonitor.add(m_dirPath, freePercent);

	doSetRecordDir(m_dirPath);

	return true;
}

/**
 * 删除录像
 * @param record
 * @return
 */
bool CMediaStorage::deleteRecord(int id)
{
	av::MRecord record;
	memset(&record, 0, sizeof(record));
	if (!m_dataStore->getRecord(id, record))
	{
		CLog::warning("can not find record by id %d\n", id);
		return false;
	}

	deleteRecord(record);

	return true;
}

void CMediaStorage::forceAac(bool enabled)
{
	CMediaStorageChannel::forceAac = enabled;
}


void CMediaStorage::onStorageEvent(const StorageEvent& event)
{
	fireStorageEvent(event);
}

void CMediaStorage::onNotEnoughSpace(const std::string& dirpath, int64_t totalSpace, int64_t freeSpace, double percent)
{
	StorageEvent event = StorageEvent();
	event.time = time(NULL);
	event.type = EVENT_STORAGE_NOSPACE;
	event.data.storage.free = freeSpace;
	comn::copyStr(event.data.storage.path, dirpath);
	event.data.storage.total = totalSpace;
	event.data.storage.percent = (float)percent;

	onStorageEvent(event);

	AlarmEvent alarmEvent;
	comn::copyStr(alarmEvent.device, event.device);
	alarmEvent.startTime = event.time;
	alarmEvent.type = ALARM_STORAGE_OVERRUN;
	comn::copyStr(alarmEvent.name, dirpath);
	comn::copyStr(alarmEvent.memo, comn::StringCast::toString(freeSpace));

	writeEvent(alarmEvent.device, alarmEvent);

	handleNotEnoughSpace(dirpath, totalSpace, freeSpace);

}

void CMediaStorage::onRecordCleared(const char* device, int cleared)
{
	StorageEvent event = StorageEvent();
	comn::copyStr(event.device, device);
	event.time = time(NULL);
	event.type = EVENT_RECORD_CLEARD;
	comn::copyStr(event.data.record.device, device);
	event.data.cleared = cleared;
	
	onStorageEvent(event);
}

void CMediaStorage::fireStorageEvent(const StorageEvent& event)
{
	if (m_sink)
	{
		m_sink->onStorageEvent(event);
	}
}

void CMediaStorage::handleNotEnoughSpace(const std::string& dirpath, int64_t totalSpace, int64_t freeSpace)
{
	if (m_coverageRecord > 0)
	{
		/// delete records
		MRecordVector records;
		records.resize(DELETE_RECORD_COUNT);
		av::MRecordQuery query = av::MRecordQuery();

		int size = m_dataStore->searchRecord(query, 0, &(records[0]), DELETE_RECORD_COUNT);
		for (int i = 0; i < size; i ++)
		{
			StorageEvent event = StorageEvent();
			event.time = time(NULL);
			event.type = EVENT_RECORD_DELETE;
			event.data.record = records[i];
			onStorageEvent(event);

			deleteRecord(records[i]);
		}
	}
	else
	{
		CLog::warning("MediaStorage auto stop record of all channels because not enough space.\n");

		doStopRecord();
	}
}

void CMediaStorage::deleteRecord(av::MRecord& record)
{
	m_dataStore->removeRecord(record.id);

	if (!comn::Path::deleteFile(record.filepath))
	{
		CLog::warning("failed to delete file %s\n", record.filepath);
	}
	std::string eventFile = CMediaStorageChannel::getEventFilePath(record.filepath, strlen(record.filepath));
	comn::Path::deleteFile(eventFile);
}

bool CMediaStorage::writeEvent(const char* device, AlarmEvent& alarmEvent)
{
	bool done = m_dataStore->addEvent(alarmEvent);
	CMediaStorageChannelPtr chl = findChannel(device);
	if (chl)
	{
	    chl->writeEvent(alarmEvent);
	}
	return done;
}

void CMediaStorage::setCoverageRecord(int count)
{
	m_coverageRecord = count;
}

bool CMediaStorage::deleteRecordWithFileName(const char* filename)
{
	if (!filename)
	{
		return false;
	}

	av::MRecord record;
	memset(&record, 0, sizeof(record));
	if (!m_dataStore->findRecord(filename, record))
	{
		return false;
	}

	deleteRecord(record);
	return true;
}

void CMediaStorage::enableRecordSubDir(bool enabled)
{
    CMediaStorageChannel::enableSubDir = enabled;
}

void CMediaStorage::setRecordNameTemplate(const char* tmpl)
{
	if ((tmpl == NULL) || (strlen(tmpl) == 0))
	{
		return;
	}

	CMediaStorageChannel::nameTemplate = tmpl;
}

bool CMediaStorage::findRecordWithFileName(const char* filename, MRecord* record)
{
	if (!filename || !record)
	{
		return false;
	}

	return m_dataStore->findRecord(filename, *record);
}

void CMediaStorage::enableCoverageEventRecord(int events)
{
	m_coverageEvents = events;
}


void CMediaStorage::clearBrokenRecord(const char* device)
{
	if (m_clearThread)
	{
		m_clearThread->stop();
		m_clearThread.reset();
	}

	m_clearThread.reset(new RecordClearThread(m_dataStore, device, this));
	m_clearThread->start();
}

void CMediaStorage::doSetRecordDir(const std::string& filepath)
{
    auto func = [&] (MediaStorageChannel*, CMediaStorageChannelPtr& chl)
    {
        chl->setRecordDir(filepath);
		return true;
    };
    m_channels.forEach(func);
}

void CMediaStorage::doSetFileFormat(RecordFileFormat fileFormat)
{
    auto func = [&] (MediaStorageChannel*, CMediaStorageChannelPtr& chl)
    {
        chl->setFileFormat(fileFormat);
		return true;
    };
    m_channels.forEach(func);
}

void CMediaStorage::doSetFileDuration(int seconds)
{
    auto func = [&] (MediaStorageChannel*, CMediaStorageChannelPtr& chl)
    {
        chl->setFileDuration(seconds);
		return true;
    };
    m_channels.forEach(func);
}

void CMediaStorage::doStopRecord()
{
    auto func = [&] (MediaStorageChannel*, CMediaStorageChannelPtr& chl)
    {
        chl->stopRecord();
		return true;
    };
    m_channels.forEach(func);
}

CMediaStorageChannelPtr CMediaStorage::findChannel(const char* device)
{
    IsDevice pred(device);
    MediaStorageChannel* key = NULL;
    CMediaStorageChannelPtr chl;
    m_channels.findIf(pred, key, chl);
    return chl;
}


}
